home *** CD-ROM | disk | FTP | other *** search
Text File | 2002-08-20 | 37.0 KB | 1,530 lines |
- /*
- ** IMAPFolder.m
- **
- ** Copyright (c) 2001, 2002
- **
- ** Author: Ludovic Marcotte <ludovic@Sophos.ca>
- ** Anthony W. Juckel <awj@digitalgreen.com>
- **
- ** This library is free software; you can redistribute it and/or
- ** modify it under the terms of the GNU Lesser General Public
- ** License as published by the Free Software Foundation; either
- ** version 2.1 of the License, or (at your option) any later version.
- **
- ** This library is distributed in the hope that it will be useful,
- ** but WITHOUT ANY WARRANTY; without even the implied warranty of
- ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- ** Lesser General Public License for more details.
- **
- ** You should have received a copy of the GNU Lesser General Public
- ** License along with this library; if not, write to the Free Software
- ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
- #import <Pantomime/IMAPFolder.h>
-
- #import <Pantomime/Connection.h>
- #import <Pantomime/Constants.h>
- #import <Pantomime/Flags.h>
- #import <Pantomime/IMAPCacheManager.h>
- #import <Pantomime/IMAPCacheObject.h>
- #import <Pantomime/IMAPStore.h>
- #import <Pantomime/IMAPMessage.h>
- #import <Pantomime/TCPConnection.h>
- #import <Pantomime/NSDataExtensions.h>
-
- @implementation IMAPFolder
-
- //
- //
- //
- - (id) initWithName: (NSString *) theName
- {
- [super initWithName: theName];
-
- [self setSelected: YES];
- [self setDelegate: nil];
-
- return self;
- }
-
-
- //
- //
- //
- - (void) dealloc
- {
- RELEASE(imapCacheManager);
-
- [super dealloc];
- }
-
-
- //
- //
- //
- - (void) appendMessageFromRawSource: (NSData *) theData
- {
- [self appendMessageFromRawSource: theData
- flags: nil];
- }
-
-
- //
- //
- //
- - (void) appendMessageFromRawSource: (NSData *) theData
- flags: (Flags *) theFlags
- {
- NSString *aString, *flagsAsString;
- BOOL newMessagesHaveArrived;
-
- IMAPStore *aStore;
- NSData *aData;
-
- newMessagesHaveArrived = NO;
-
- // We always NOOP before doing anything
- [self noop];
-
- if ( theFlags )
- {
- flagsAsString = [self _flagsAsStringFromFlags: theFlags];
- }
- else
- {
- flagsAsString = @"";
- }
-
- // We remove any invalid headers from our message
- aData = [self _removeInvalidHeadersFromMessage: theData];
-
- // We obtain the pointer to our store
- aStore = (IMAPStore *)[self store];
-
- // We write our IMAP command
- [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ APPEND \"%@\" (%@) {%d}",
- [aStore nextTag], // tag
- [self name], // folder name
- flagsAsString, // flags
- [aData length]]]; // length of message in bytes
-
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
-
- if ( [aString hasPrefix: @"+"] )
- {
- // We write our Message
- [[aStore tcpConnection] writeData: aData];
-
- // We send an empty line (just \r\n)
- [[aStore tcpConnection] writeLine: @""];
-
- // We read the response from our IMAP server
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
-
- while ( ![aString hasPrefix: [NSString stringWithFormat: @"%@ OK", [aStore lastTag]]] )
- {
- if ( [aString hasCaseInsensitiveSuffix: @"EXISTS"] )
- {
- int aCount;
-
- aCount = [aStore parseExists: aString];
-
- if (aCount > [self count])
- {
- newMessagesHaveArrived = YES;
- }
- }
- else if ( [aString hasPrefix: [NSString stringWithFormat: @"%@ NO", [aStore lastTag]]] ||
- [aString hasPrefix: [NSString stringWithFormat: @"%@ BAD", [aStore lastTag]]] )
- {
- NSException *anException;
-
- NSDebugLog(@"IMAPFolder: APPEND failed %@", aString);
-
- anException = [NSException exceptionWithName: @"PantomimeFolderAppendMessageException"
- reason: aString
- userInfo: nil];
- [anException raise];
- }
-
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
- }
-
- while ( ![aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"%@ OK",
- [aStore lastTag]] ])
- {
- if ( [aString hasCaseInsensitiveSuffix: @"EXISTS"] )
- {
- int aCount;
-
- aCount = [aStore parseExists: aString];
-
- //NSDebugLog(@"IMAPFolder: new count = %d, previous = %d", aCount, [self count]);
-
- if (aCount > [self count])
- {
- newMessagesHaveArrived = YES;
- }
- }
-
- // We just read the lines until the end
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
- }
- }
- else
- {
- NSException *anException;
-
- NSDebugLog(@"IMAPFolder: APPEND failed %@", aString);
-
- anException = [NSException exceptionWithName: @"PantomimeFolderAppendMessageException"
- reason: aString
- userInfo: nil];
- [anException raise];
- }
-
- if ( newMessagesHaveArrived )
- {
- [self _newMessagesHaveArrived];
- }
- }
-
- //
- //
- //
- - (void) deleteMessageWithUID: (int) theUID
- {
- Flags *theFlags;
-
- theFlags = [[Flags alloc] init];
- AUTORELEASE(theFlags);
-
- [theFlags add: DELETED];
-
- [self setMessageFlags: theFlags
- forUID: theUID];
- }
-
-
- //
- // This method is used to fetch a complete message for the specified uid
- // from the current folder.
- //
- - (NSData *) prefetchMessageWithUID: (int) theUID
- {
- IMAPStore *aStore;
- NSString *aString;
- int msn;
-
- // We obtain the pointer to our store
- aStore = (IMAPStore *)[self store];
-
- // We first ask for the msn of the message
- msn = [self fetchMessageMSNWithUID: theUID];
-
- // We ask for the body of the message
- [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ UID FETCH %d:%d RFC822",
- [aStore nextTag], theUID, theUID]];
-
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
-
- // if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"* %d FETCH (UID %d RFC822",
- if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"* %d FETCH (",
- msn]] )
- {
- NSMutableData *aMessage;
-
- aMessage = [[NSMutableData alloc] initWithData: [[aStore tcpConnection]
- readDataOfLength:
- [self parseMessageSegmentSizeFromString:
- aString]] ];
-
- // We must replace all occurence of \r\n by \n in the message.
- [self _replaceCRLFInMutableData: aMessage];
-
- // The server response following our RFC822 can be:
- // )
- // 0003 OK UID FETCH
- // OR
- // )
- // 0003 OK Completed
- //
- // We just read those two lines and discard them.
- [[aStore tcpConnection] readLineBySkippingCR: YES];
- [[aStore tcpConnection] readLineBySkippingCR: YES];
-
- return AUTORELEASE(aMessage);
- }
-
- return nil;
- }
-
-
- //
- // This method is used to fetch the message body for the specified uid.
- //
- - (NSData *) prefetchMessageBodyWithUID: (int) theUID
- {
- IMAPStore *aStore;
- NSString *aString;
- int msn;
-
- // We always NOOP before doing anything
- [self noop];
-
- // We obtain the pointer to our store
- aStore = (IMAPStore *)[self store];
-
- // We first ask for the msn of the message
- msn = [self fetchMessageMSNWithUID: theUID];
-
- // We ask for the body of the message
- [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ UID FETCH %d:%d BODY[TEXT]",
- [aStore nextTag], theUID, theUID]];
-
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
-
- // if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"* %d FETCH (UID %d BODY[TEXT]",
- if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"* %d FETCH (",
- msn]] )
- {
- NSMutableData *aBody;
-
- aBody = [[NSMutableData alloc] initWithData:
- [[aStore tcpConnection]
- readDataOfLength:
- [self parseMessageSegmentSizeFromString: aString]] ];
-
- // We must replace all occurence of \r\n by \n in the body of the message.
- [self _replaceCRLFInMutableData: aBody];
-
-
- // The server response following our BODY[TEXT] can be:
- // )
- // 0003 OK UID FETCH
- // OR
- // )
- // 0003 OK Completed
- //
- // We just read those two lines and discard them.
- [[aStore tcpConnection] readLineBySkippingCR: YES]; // )
- [[aStore tcpConnection] readLineBySkippingCR: YES]; // 0003 OK ...
-
- return AUTORELEASE(aBody);
- }
-
- NSDebugLog(@"IMAPFolder: Returning nil in IMAPFolder: -prefetchMessageBodyWithUID...");
- return nil;
- }
-
- //
- // This method is used to fetch a message headers for the specified uid.
- //
- - (NSData *) prefetchMessageHeadersWithUID: (int) theUID
- {
- IMAPStore *aStore;
- NSString *aString;
- int msn;
-
- // We obtain the pointer to our store
- aStore = (IMAPStore *)[self store];
-
- // We first ask for the msn of the message
- msn = [self fetchMessageMSNWithUID: theUID];
-
- // We ask for the headers of the message
- [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ UID FETCH %d:%d BODY[HEADER]",
- [aStore nextTag], theUID, theUID]];
-
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
-
- //NSDebugLog(@"prefetchMessageHeadersWithUID aString = |%@|", aString);
-
- //
- // The response from the server is like that: * 2 FETCH (UID 873 BODY[HEADER] {1391}
- // * 8 FETCH (FLAGS (\Recent \Seen) UID 9 BODY[HEADER] {2131}
- //
- if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"* %d FETCH (", msn]] )
- {
- NSMutableData *aMutableData;
-
- aMutableData = [[NSMutableData alloc] init];
-
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
-
- //
- // We can receive from the server: 0003 OK Completed
- // 0003 OK UID FETCH Completed
- //
- while ( ![aString hasCaseInsensitivePrefix: [NSString stringWithFormat:@"%@ OK",
- [aStore lastTag]] ] )
- {
- // We don't append IMAP's extra stuff like:
- // )
- // (empty line)
- if ( [aString length] == 1 && [aString isEqualToString: @")"] )
- {
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
- }
- else if ([aString length] == 0)
- {
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
- }
- //else if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat:@"* %d FETCH", msn]] )
- // {
- // aString = [aStore lineFromSocket];
- // }
- else
- {
- // FIXME: Everything is legal here? (cString call..)
- [aMutableData appendCString: [aString cString]];
- [aMutableData appendCString: "\n"];
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
- }
- }
-
- return AUTORELEASE(aMutableData);
- }
-
- NSDebugLog(@"IMAPFolder: Returning nil in IMAPFolder: -prefetchMessageHeadersWithUID...");
- return nil;
- }
-
-
- //
- // This method is used to cache the messages from the POP3 server
- // locally (in memory).
- //
- - (BOOL) prefetch
- {
- BOOL didTransferMessages;
- IMAPMessage *aMessage;
- Flags *theFlags;
- int i, last_uid;
-
- // For our cache
- IMAPCacheObject *anObject;
-
- didTransferMessages = NO;
- last_uid = 0;
-
- // We first init from our cache if we need too
- if ( [self imapCacheManager] )
- {
- NSArray *imapCacheObjects;
- NSDictionary *serverFlags;
-
- imapCacheObjects = [[self imapCacheManager] imapCacheObjects];
-
- if ( [imapCacheObjects count] > 0 )
- {
- NSAutoreleasePool *pool;
- NSMutableArray *cacheObjectsToDelete;
-
- // We must refetch the flags in case they have changed
- // This is done in bulk, for all messages in the cache
- serverFlags = [self prefetchMessageFlagsFromUID: [[imapCacheObjects objectAtIndex: 0] uid]
- toUID: [[imapCacheObjects lastObject] uid] ];
- RETAIN(serverFlags);
-
-
- pool = [[NSAutoreleasePool alloc] init];
- cacheObjectsToDelete = [[NSMutableArray alloc] init];
-
- for (i = 0; i < [imapCacheObjects count]; i++)
- {
- Flags *previousFlags;
-
- if ( (i % 100) == 0 )
- {
- TEST_RELEASE(pool);
- pool = [[NSAutoreleasePool alloc] init];
- }
-
- anObject = [imapCacheObjects objectAtIndex: i];
-
- theFlags = (Flags *)[serverFlags objectForKey: [NSNumber numberWithInt: [anObject uid]]];
-
- // If serverFlags does not contain theFlags for a particular message UID,
- // that message must have been deleted from the server. We just go
- // at the end of the loop (ie., we don't initialize a new message)
- if ( theFlags == nil )
- {
- [cacheObjectsToDelete addObject: anObject];
- continue;
- }
-
- aMessage = (IMAPMessage *)[anObject message];
-
- [aMessage setInitialized: NO];
- [aMessage setFolder: self];
- [aMessage setMessageNumber: [anObject uid]];
-
- // FIXME - See the RFC to know what to do about cache coherency for deleted mails
- // We now set our new flags
- previousFlags = [aMessage flags];
- RETAIN(previousFlags);
-
- [aMessage setFlags: theFlags];
-
- // We verify we must add any extra flag we had before
- if ( [previousFlags contain: DELETED] )
- {
- [[aMessage flags] add: DELETED];
- }
-
- if ( [previousFlags contain: TRANSFERRED] )
- {
- [[aMessage flags] add: TRANSFERRED];
- }
-
- // FIXME - That one should come from the IMAP server, not
- // from the cache but, it is NOT saved right now
- // on the IMAP server.
- if ( [previousFlags contain: ANSWERED] )
- {
- [[aMessage flags] add: ANSWERED];
- }
-
-
- RELEASE(previousFlags);
-
- [self appendMessage: aMessage];
-
- // We set the last UID
- last_uid = [anObject uid];
-
- } // for (...)
-
- // Let's remove from our cache the messages that have been deleted from
- // the IMAP server
- for (i = 0; i < [cacheObjectsToDelete count]; i++)
- {
- [[self imapCacheManager] removeObject: [cacheObjectsToDelete objectAtIndex: i]];
- }
- RELEASE(cacheObjectsToDelete);
-
- RELEASE(pool);
-
- RELEASE(serverFlags);
- }
- }
-
- didTransferMessages = [self prefetchNewMessagesStartingAtUID: (last_uid + 1)];
- NSDebugLog(@"IMAPFolder: Done prefetching messages");
- return didTransferMessages;
- }
-
- //
- //
- //
- - (BOOL) prefetchNewMessagesStartingAtUID: (int) theUID
- {
- BOOL didTransferMessages;
- IMAPMessage *aMessage;
- IMAPStore *aStore;
- Flags *theFlags;
-
- NSString *aString;
- NSMutableData *aHeader;
- int uid, size, msn;
-
- // For our cache
- IMAPCacheObject *anObject;
-
- didTransferMessages = NO;
- // Request summary information about all messages
- aStore = (IMAPStore *)[self store];
-
- [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ UID FETCH %d:* (FLAGS RFC822.SIZE BODY.PEEK[HEADER])",
- [aStore nextTag], theUID] ];
-
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
-
- while ( [aString hasCaseInsensitivePrefix: @"* "] )
- {
- NSAutoreleasePool *pool;
-
- pool = [[NSAutoreleasePool alloc] init];
-
- // First, we read the MSN;
- msn = [self parseMessageMSNFromString: [aString substringFromIndex: 2]];
-
- // Now, check if the UID is included on the first line
- uid = [self parseMessageUIDFromString: aString];
-
- // Then, we read the flags;
- theFlags = [self parseMessageFlagsFromString: aString];
-
- // Then, we read the size;
- size = [self parseMessageSizeFromString: aString];
-
- // Then, we read the header;
- aHeader = [[NSMutableData alloc] initWithData:
- [[aStore tcpConnection] readDataOfLength:
- [self parseMessageSegmentSizeFromString: aString]] ];
-
- // We must replace all occurence of \r\n by \n in the headers of the message.
- [self _replaceCRLFInMutableData: aHeader];
-
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
-
- // Finally, we may need to read the UID (which may be at the end of the string);
- if ( !uid )
- {
- uid = [self parseMessageUIDFromString: aString];
- }
-
- // Most IMAP server will *always* return the LAST UID even if we are prefetching *from*
- // that UID. We must just skip it in case that happens.
- if ( uid > (theUID - 1) )
- {
- // The string read is valid, we can now add the message to our folder;
- aMessage = [[IMAPMessage alloc] initWithHeadersFromData: aHeader];
-
- // We set some initial properties to our message;
- [aMessage setInitialized: NO];
- [aMessage setFolder: self];
- [aMessage setMessageNumber: uid];
- [aMessage setFlags: theFlags];
- [aMessage setSize: size];
-
- // We create our cached object;
- anObject = [[IMAPCacheObject alloc] initWithUID: uid
- message: aMessage];
-
- // NSDebugLog(@"IMAPFolder: Adding new object with UID: %d", uid);
- [self appendMessage: aMessage];
- RELEASE(aMessage);
-
- didTransferMessages = YES;
-
- // We add our new entry to our cache;
- if ( [self imapCacheManager] )
- {
- [[self imapCacheManager] addObject: anObject];
- }
-
- RELEASE(anObject);
- }
-
- RELEASE(pool);
-
- // Finally, we read the next line from the IMAP server;
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
-
- } /* while */
-
- // We synchronize our cache
- if ( [self imapCacheManager] )
- {
- [[self imapCacheManager] synchronize];
- }
-
- return didTransferMessages;
- }
-
- //
- //
- //
- - (NSDictionary *) prefetchMessageFlagsFromUID: (int) startUID
- toUID: (int) endUID
- {
- NSMutableDictionary *serverFlags;
- NSAutoreleasePool *pool;
- IMAPStore *aStore;
- Flags *theFlags;
- NSString *aString;
- int uid;
-
- serverFlags = [NSMutableDictionary dictionaryWithCapacity: 100];
-
- aStore = (IMAPStore *)[self store];
-
- // We send our IMAP command
- [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ UID FETCH %d:%d (FLAGS)", [aStore nextTag], startUID, endUID]];
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
-
- pool = [[NSAutoreleasePool alloc] init];
-
- while ( [aString hasCaseInsensitivePrefix: @"* "] )
- {
- uid = [self parseMessageUIDFromString: aString];
- theFlags = [self parseMessageFlagsFromString: aString];
-
- [serverFlags setObject: theFlags
- forKey: [NSNumber numberWithInt:uid]];
-
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
- }
-
- RELEASE(pool);
-
- return serverFlags;
- }
-
-
- //
- // This method simply close the selected mailbox (ie. folder)
- //
- - (void) close
- {
- IMAPStore *aStore;
- NSString *aString;
-
- if ( ![self selected])
- {
- return;
- }
-
- // We sync our cache manager if we have one
- if ( [self imapCacheManager] )
- {
- NSDebugLog(@"IMAPFolder: Synchronizing the IMAP cache manager...");
- [[self imapCacheManager] synchronize];
- }
-
- // We obtain the pointer to our store
- aStore = (IMAPStore *)[self store];
-
- // We ask for the headers of the message
- [[aStore tcpConnection] writeLine: [NSString stringWithFormat:@"%@ CLOSE", [aStore nextTag]]];
-
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
-
- //
- // The response from the server can be: 0004 OK Completed
- // 0004 OK CLOSE completed
- //
- if (! [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"%@ OK",
- [aStore lastTag]] ])
- {
- NSDebugLog(@"IMAPFolder: An error occured while closing the current folder.");
- }
-
- // We remove our current folder from the list of opened folders in the store
- [aStore removeFolderFromOpenedFolders: self];
- }
-
-
- //
- // This method returns all messages that have the flag 'deleted'
- // All the returned message ARE IN RAW SOURCE.
- //
- // This method DOES NOT return a message flagged as TRANSFERRED
- //
- - (NSArray *) expunge: (BOOL) returnDeletedMessages
- {
- NSMutableArray *aMutableArray;
- NSString *aString;
- IMAPStore *aStore;
- int i;
-
- aMutableArray = [[NSMutableArray alloc] init];
-
- for (i = [allMessages count] - 1; i >= 0; i--)
- {
- IMAPMessage *aMessage;
-
- aMessage = (IMAPMessage *)[allMessages objectAtIndex: i];
-
- // If the message has been flagged as DELETED
- if ( [[aMessage flags] contain: DELETED] ||
- [[aMessage flags] contain: TRANSFERRED] )
- {
- // We add it to our array of returned message
- if ( [[aMessage flags] contain: DELETED] &&
- returnDeletedMessages )
- {
- NSData *aData = [aMessage rawSource];
- [aMutableArray addObject: aData];
- }
-
- [self deleteMessageWithUID: [aMessage messageNumber] ];
- [super removeMessage: aMessage];
-
- // We remove its entry in our cache
- if ( [self imapCacheManager] )
- {
- IMAPCacheObject *anObject;
-
- anObject = [[self imapCacheManager] findIMAPCacheObject: [aMessage messageNumber]];
- [[self imapCacheManager] removeObject: anObject];
- }
- }
- }
-
- // We sync our cache manager if we have one
- if ( [self imapCacheManager] )
- {
- NSDebugLog(@"IMAPFolder: Synchronizing the IMAP cache manager...");
- [[self imapCacheManager] synchronize];
- }
-
- // We obtain the pointer to our store
- aStore = (IMAPStore *)[self store];
-
- // We finally call EXPUNGE on our IMAP server
- [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ EXPUNGE",
- [aStore nextTag]] ];
-
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
-
- while ( ![aString hasCaseInsensitivePrefix: [NSString stringWithFormat:@"%@ OK",
- [aStore lastTag]] ] )
- {
- // If we got an error, we stop.
- if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat:@"%@ NO", [aStore lastTag]]] ||
- [aString hasCaseInsensitivePrefix: [NSString stringWithFormat:@"%@ NO", [aStore lastTag]]] )
- {
- NSDebugLog(@"IMAPFolder: Error occured in -expunge");
- return [NSArray array];
- }
-
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
- }
-
- return AUTORELEASE(aMutableArray);
- }
-
-
- //
- //
- //
- - (Flags *) fetchMessageFlagsWithUID: (int) theUID
- {
- IMAPStore *aStore;
- NSString *aString;
- Flags *theFlags;
- int msn;
-
- // We first ask for the msn of the message
- msn = [self fetchMessageMSNWithUID: theUID];
-
- // We initialize our Flags
- theFlags = [[Flags alloc] init];
-
- // We obtain the pointer to our store
- aStore = (IMAPStore *)[self store];
-
-
- // We ask for the body of the message
- [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ UID FETCH %d:%d FLAGS",
- [aStore nextTag], theUID, theUID]];
-
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
-
- //
- // The server response is like: * 1 FETCH (UID 872 FLAGS (\Seen))
- // * 1 FETCH (FLAGS (\Seen) UID 1)
- //
- if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"* %d FETCH (", msn]] )
- {
- NSRange aRange;
-
- // We check if the message has the Seen or Recent flag
- aRange = [aString rangeOfString: @"\\Seen" options: NSCaseInsensitiveSearch];
-
- if ( aRange.length > 0 )
- {
- [theFlags add: SEEN];
- }
- else
- {
- [theFlags add: RECENT];
- }
-
- // We check if the message has the Deleted flag
- aRange = [aString rangeOfString: @"\\Deleted" options: NSCaseInsensitiveSearch];
-
- if ( aRange.length > 0 )
- {
- [theFlags add: DELETED];
- }
-
- // We check if the message has the Answered flag
- aRange = [aString rangeOfString: @"\\Answered" options: NSCaseInsensitiveSearch];
-
- if ( aRange.length > 0 )
- {
- [theFlags add: ANSWERED];
- }
-
- // We finally read the extra line on our socket and we discard it.
- [[aStore tcpConnection] readLineBySkippingCR: YES];
- }
-
- return AUTORELEASE(theFlags);
- }
-
-
- //
- //
- //
- - (int) fetchMessageSizeWithUID: (int) theUID
- {
- IMAPStore *aStore;
- NSString *aString;
- int size, msn;
-
- // We first ask for the msn of the message
- msn = [self fetchMessageMSNWithUID: theUID];
-
- size = 0;
-
- // We obtain the pointer to our store
- aStore = (IMAPStore *)[self store];
-
- // We ask for the body of the message
- [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ UID FETCH %d:%d RFC822.SIZE",
- [aStore nextTag], theUID, theUID]];
-
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
-
- // The server response is like: * 1 FETCH (UID 872 RFC822.SIZE 4222)
- // if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"* %d FETCH (UID %d RFC822.SIZE",
- if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"* %d FETCH (",
- msn]] )
- {
- NSRange aRange;
-
- aRange = [aString rangeOfString: @"RFC822.SIZE"];
-
- if (aRange.length > 0)
- {
- int mark;
-
- mark = aRange.location + aRange.length + 1;
- aString = [aString substringWithRange: NSMakeRange(mark, [aString length] - mark - 1)];
- size = [aString intValue];
- }
-
- // We finally read the extra line on our socket and we discard it.
- [[aStore tcpConnection] readLineBySkippingCR: YES];
- }
-
- return size;
- }
-
-
-
- //
- //
- //
- - (int) fetchMessageMSNWithUID: (int) theUID
- {
- IMAPStore *aStore;
- NSString *aString;
-
- // We obtain the pointer to our store
- aStore = (IMAPStore *)[self store];
-
-
- // We ask for the UID of the message
- [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ UID FETCH %d:%d UID",
- [aStore nextTag], theUID, theUID]];
-
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
-
- //
- // The server response is like: * 46 FETCH (UID 960)
- //
- if ( [aString hasCaseInsensitivePrefix: @"* "] )
- {
- NSRange aRange;
-
- aString = [aString substringFromIndex: 2];
- aRange = [aString rangeOfString: @" "];
-
- if (aRange.length > 0)
- {
- aString = [aString substringWithRange: NSMakeRange(0, aRange.location)];
- }
-
- // We finally read the extra line on our socket and we discard it.
- [[aStore tcpConnection] readLineBySkippingCR: YES];
- }
-
- return [aString intValue];
- }
-
- //
- //
- //
- - (Flags *) parseMessageFlagsFromString: (NSString *) aString
- {
- NSRange aRange;
- Flags *theFlags;
-
- theFlags = [[Flags alloc] init];
-
- // We check if the message has the Seen or Recent flag
- aRange = [aString rangeOfString: @"\\Seen"
- options: NSCaseInsensitiveSearch];
-
- if ( aRange.length > 0 )
- {
- [theFlags add: SEEN];
- }
- else
- {
- [theFlags add: RECENT];
- }
-
- // We check if the message has the Deleted flag
- aRange = [aString rangeOfString: @"\\Deleted"
- options: NSCaseInsensitiveSearch];
-
- if ( aRange.length > 0 )
- {
- [theFlags add: DELETED];
- }
-
- // We check if the message has the Answered flag
- aRange = [aString rangeOfString: @"\\Answered"
- options: NSCaseInsensitiveSearch];
-
- if ( aRange.length > 0 )
- {
- [theFlags add: ANSWERED];
- }
-
- // We check if the message has the Flagged flag
- aRange = [aString rangeOfString: @"\\Flagged"
- options: NSCaseInsensitiveSearch];
-
- if ( aRange.length > 0 )
- {
- [theFlags add: FLAGGED];
- }
-
- // We check if the message has the Draft flag
- aRange = [aString rangeOfString: @"\\Draft"
- options: NSCaseInsensitiveSearch];
-
- if ( aRange.length > 0 )
- {
- [theFlags add: DRAFT];
- }
-
- return AUTORELEASE(theFlags);
- }
-
- //
- //
- //
- - (int) parseMessageSizeFromString: (NSString *) aString
- {
- NSRange aRange;
-
- aRange = [aString rangeOfString: @"RFC822.SIZE"];
-
- if (aRange.length > 0)
- {
- int mark;
-
- mark = aRange.location + aRange.length + 1;
-
- aRange = [aString rangeOfString: @" "
- options: 0
- range: NSMakeRange(mark, [aString length] - mark - 1)];
-
- return [[aString substringWithRange: NSMakeRange(mark, aRange.location - mark)] intValue];
- }
- else
- {
- return 0;
- }
- }
-
- //
- //
- //
- - (int) parseMessageMSNFromString: (NSString *) aString
- {
- NSRange aRange;
-
- aRange = [aString rangeOfString: @" "];
-
- if (aRange.length > 0)
- {
- return [[aString substringWithRange: NSMakeRange(0, aRange.location)] intValue];
- }
- else
- {
- return 0;
- }
- }
-
- //
- //
- //
- - (int) parseMessageSegmentSizeFromString: (NSString *) aString
- {
- NSRange aRange;
-
- aRange = [aString rangeOfString: @"{"];
-
- if (aRange.length > 0)
- {
- int mark;
-
- mark = aRange.location + aRange.length;
- aRange = [aString rangeOfString: @"}"
- options: 0
- range: NSMakeRange(mark, [aString length] - mark)];
-
- return [[aString substringWithRange: NSMakeRange(mark, aRange.location - mark)] intValue];
- }
- else
- {
- return 0;
- }
- }
-
- //
- //
- //
- - (int) parseMessageUIDFromString: (NSString *) aString
- {
- NSRange aRange;
-
- aRange = [aString rangeOfString: @"UID"];
-
- if( aRange.length > 0 )
- {
- int mark = aRange.location + aRange.length + 1;
- aRange = [aString rangeOfString: @" "
- options: 0
- range: NSMakeRange(mark, [aString length] - mark)];
-
- if( aRange.length > 0 )
- {
- return [[aString substringWithRange: NSMakeRange(mark, aRange.location - mark)] intValue];
- }
- else
- {
- aRange = [aString rangeOfString: @")"
- options: 0
- range: NSMakeRange(mark, [aString length] - mark)];
-
- if( aRange.length > 0 )
- {
- return [[aString substringWithRange: NSMakeRange(mark, aRange.location - mark)] intValue];
- }
- else
- {
- return 0;
- }
- }
- }
- else
- {
- return 0;
- }
- }
-
- //
- // Return an array of NSNumber
- //
- - (NSArray *) uncachedUIDStartingAtUID: (int) theUID
- {
- NSMutableArray *aMutableArray;
- IMAPStore *aStore;
- NSString *aString;
-
- NSDebugLog(@"IMAPFolder: Starting search from UID = %d", theUID);
-
- // We obtain the pointer to our store
- aStore = (IMAPStore *)[self store];
-
- // We init our return value
- aMutableArray = [[NSMutableArray alloc] init];
-
- // We ask for the list of UIDs
- [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ UID FETCH %d:* UID",
- [aStore nextTag], theUID]];
-
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
-
- while ( [aString hasCaseInsensitivePrefix: @"* "] )
- {
- NSRange aRange;
-
- aRange = [aString rangeOfString: @"FETCH (UID"];
-
- if (aRange.length > 0)
- {
- aString = [aString substringWithRange: NSMakeRange(aRange.location + aRange.length + 1,
- [aString length] - (aRange.location + aRange.length) - 2)];
-
- //NSDebugLog(@"Adding uid from string = |%@|", aString);
- [aMutableArray addObject: [NSNumber numberWithInt: [aString intValue]]];
- }
-
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
- }
-
- return AUTORELEASE(aMutableArray);
- }
-
-
- //
- //
- //
- - (void) setMessageFlags: (Flags *) theFlags
- forUID: (int) theUID
- {
- IMAPStore *aStore;
- NSString *aString;
- NSMutableString *flagString;
-
- if (! theFlags )
- {
- return;
- }
-
- // We obtain the pointer to our store
- aStore = (IMAPStore *)[self store];
-
- // We create our string of flags
- flagString = [[NSMutableString alloc] init];
-
- if ( [theFlags contain: DELETED] )
- {
- [flagString appendString: @"\\Deleted"];
- }
-
-
- // Set the flag to \Deleted to the message at the specified index
- [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ UID STORE %d:%d +FLAGS (%@)",
- [aStore nextTag], theUID, theUID, flagString]];
-
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
-
- if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"%@ NO", [aStore lastTag]]] ||
- [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"%@ BAD", [aStore lastTag]]] )
- {
- NSDebugLog(@"IMAPFolder: An error occured while setting the flags of message the message with UID = %@", theUID);
- }
- else
- {
- //
- // The response can be: 0005 OK UID STORE
- // 0003 OK Completed
- //
- while (! [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"%@ OK",
- [aStore lastTag]] ])
- {
- // We just read the lines until the end
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
- }
- }
-
- RELEASE(flagString);
- }
-
-
- //
- //
- //
- - (void) noop
- {
- IMAPStore *aStore;
- NSString *aString;
- BOOL newMessagesHaveArrived;
-
- newMessagesHaveArrived = NO;
-
- // We obtain the pointer to our store
- aStore = (IMAPStore *)[self store];
-
- // Set the flag to \Deleted to the message at the specified index
- [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ NOOP", [aStore nextTag]]];;
-
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
-
- //NSDebugLog(@"aString = |%@|", aString);
-
- if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"%@ OK", [aStore lastTag]]] ||
- [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"%@ BAD", [aStore lastTag]]] )
- {
- return;
- }
- else
- {
- //
- // The server response can be: 0004 OK NOOP
- // 0004 OK Completed
- //
- while ( ![aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"%@ OK",
- [aStore lastTag]] ])
- {
- if ( [aString hasCaseInsensitiveSuffix: @"EXISTS"] )
- {
- int aCount;
-
- aCount = [aStore parseExists: aString];
-
- //NSDebugLog(@"IMAPFolder: new count = %d, previous = %d", aCount, [self count]);
-
- if (aCount > [self count])
- {
- newMessagesHaveArrived = YES;
- }
- }
-
- // We just read the lines until the end
- aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
- }
- }
-
- if ( newMessagesHaveArrived )
- {
- [self _newMessagesHaveArrived];
- }
- }
-
- //
- //
- //
- - (int) uidValidity
- {
- return uidValidity;
- }
-
-
- //
- //
- //
- - (void) setUIDValidity: (int) theUIDValidity
- {
- NSDebugLog(@"IMAPFolder: UIDVALIDITY = %d", theUIDValidity);
- uidValidity = theUIDValidity;
- }
-
-
- //
- //
- //
- - (IMAPCacheManager *) imapCacheManager
- {
- return imapCacheManager;
- }
-
-
- //
- //
- //
- - (void) setIMAPCacheManager: (IMAPCacheManager *) theIMAPCacheManager
- {
- if ( theIMAPCacheManager )
- {
- RETAIN(theIMAPCacheManager);
- RELEASE(imapCacheManager);
- imapCacheManager = theIMAPCacheManager;
- }
- else
- {
- RELEASE(imapCacheManager);
- imapCacheManager = nil;
- }
- }
-
-
- //
- //
- //
- - (BOOL) selected
- {
- return selected;
- }
-
- - (void) setSelected: (BOOL) aBOOL
- {
- selected = aBOOL;
- }
-
- //
- // This method re-implement the super method to allow
- // to set various flags.
- //
- - (void) removeMessage: (Message *) theMessage
- {
- [[theMessage flags] add: TRANSFERRED];
- [super removeMessage: theMessage];
- }
-
-
- //
- //
- //
- - (id) delegate
- {
- return delegate;
- }
-
- - (void) setDelegate: (id) theDelegate
- {
- if ( theDelegate )
- {
- RETAIN(theDelegate);
- RELEASE(delegate);
- delegate = theDelegate;
- }
- else
- {
- DESTROY(delegate);
- }
- }
-
- @end
-
-
- //
- // Private methods
- //
- @implementation IMAPFolder (Private)
-
- //
- //
- //
- - (NSData *) _removeInvalidHeadersFromMessage: (NSData *) theMessage
- {
- NSMutableData *aMutableData;
- NSArray *allLines;
- int i;
-
- // We allocate our mutable data object
- aMutableData = [[NSMutableData alloc] initWithCapacity: [theMessage length]];
-
- // We now replace all \n by \r\n
- allLines = [theMessage componentsSeparatedByCString: "\n"];
-
- for (i = 0; i < [allLines count]; i++)
- {
- NSData *aLine;
-
- // We get a line...
- aLine = [allLines objectAtIndex: i];
-
- // We skip dumb headers
- if ( [aLine hasCPrefix: "From "] )
- {
- continue;
- }
-
- [aMutableData appendData: aLine];
- [aMutableData appendCString: "\r\n"];
- }
-
- return AUTORELEASE(aMutableData);
- }
-
-
- //
- //
- //
- - (void) _replaceCRLFInMutableData: (NSMutableData *) theMutableData
- {
- unsigned char *bytes, *bi, *bo;
- int delta, i,length;
-
- bytes = [theMutableData mutableBytes];
- length = [theMutableData length];
- bi = bo = bytes;
-
- for (i = delta = 0; i < length; i++, bi++)
- {
- if ( i+1 < length && bi[0] == '\r' && bi[1] == '\n')
- {
- i++;
- bi++;
- delta++;
- }
-
- *bo = *bi;
- bo++;
- }
-
- [theMutableData setLength: length-delta];
- }
-
-
- //
- //
- //
- - (NSString *) _flagsAsStringFromFlags: (Flags *) theFlags
- {
- NSMutableString *aMutableString;
-
- aMutableString = [[NSMutableString alloc] init];
- AUTORELEASE(aMutableString);
-
- if ( [theFlags contain: ANSWERED] )
- {
- [aMutableString appendString: @"\\Answered "];
- }
-
- if ( [theFlags contain: DRAFT] )
- {
- [aMutableString appendString: @"\\Draft "];
- }
-
- if ( [theFlags contain: FLAGGED] )
- {
- [aMutableString appendString: @"\\Flagged "];
- }
-
- if ( [theFlags contain: SEEN] )
- {
- [aMutableString appendString: @"\\Seen "];
- }
-
- if ( [theFlags contain: DELETED] )
- {
- [aMutableString appendString: @"\\Deleted "];
- }
-
- return [aMutableString stringByTrimmingWhiteSpaces];
- }
-
-
- //
- //
- //
- - (void) _newMessagesHaveArrived
- {
- SEL aSelector;
-
- int last_uid;
-
- last_uid = 0;
-
- if ( [self imapCacheManager] )
- {
- NSArray *imapCacheObjects;
- IMAPCacheObject *anObject;
-
- imapCacheObjects = [[self imapCacheManager] imapCacheObjects];
-
- anObject = [imapCacheObjects lastObject];
-
- last_uid = [anObject uid];
- }
-
- [self prefetchNewMessagesStartingAtUID: (last_uid + 1)];
-
- aSelector = @selector(newMessagesWereReceived:);
-
- if ( delegate && [delegate respondsToSelector: aSelector] )
- {
- [delegate performSelector: aSelector
- withObject: self];
- }
- }
-
- @end
-
-